import type { Diff as TextDiff } from '@sanity/diff-match-patch';

import { heuristicIsHtml } from './RenderHtml';
import { heuristicLegacyImageBlocks, heuristicMarkdownImageReferenceBlocks } from './RenderImage';

// Block types
export type Block = CodeBlock | DiffBlock | HtmlBlock | ImageBlock | LatexBlock | TextBlock;
export type CodeBlock = { type: 'code'; blockTitle: string; blockCode: string; complete: boolean; };
export type DiffBlock = { type: 'diff'; textDiffs: TextDiff[] };
export type HtmlBlock = { type: 'html'; html: string; };
export type ImageBlock = { type: 'image'; url: string; alt?: string }; // Added optional alt property
export type LatexBlock = { type: 'latex'; latex: string; };
export type TextBlock = { type: 'text'; content: string; }; // for Text or Markdown


export function areBlocksEqual(a: Block, b: Block): boolean {
  if (a.type !== b.type)
    return false;

  switch (a.type) {
    case 'code':
      return a.blockTitle === (b as CodeBlock).blockTitle && a.blockCode === (b as CodeBlock).blockCode && a.complete === (b as CodeBlock).complete;
    case 'diff':
      return false; // diff blocks are never equal
    case 'html':
      return a.html === (b as HtmlBlock).html;
    case 'image':
      return a.url === (b as ImageBlock).url && a.alt === (b as ImageBlock).alt;
    case 'latex':
      return a.latex === (b as LatexBlock).latex;
    case 'text':
      return a.content === (b as TextBlock).content;
  }
}


export function parseMessageBlocks(text: string, disableParsing: boolean, forceTextDiffs?: TextDiff[]): Block[] {
  if (disableParsing)
    return [{ type: 'text', content: text }];

  if (forceTextDiffs && forceTextDiffs.length >= 1)
    return [{ type: 'diff', textDiffs: forceTextDiffs }];

  // special case: this could be generated by a proxy that returns an HTML page instead of the API response
  if (heuristicIsHtml(text))
    return [{ type: 'html', html: text }];

  // special case: markdown image references (e.g. ![alt text](https://example.com/image.png))
  const mdImageBlocks = heuristicMarkdownImageReferenceBlocks(text);
  if (mdImageBlocks)
    return mdImageBlocks;

  // special case: legacy prodia images
  const legacyImageBlocks = heuristicLegacyImageBlocks(text);
  if (legacyImageBlocks)
    return legacyImageBlocks;

  const regexPatterns = {
    codeBlock: /`{3,}([\w\x20\\.+-_]+)?\n([\s\S]*?)(`{3,}\n?|$)/g,
    latexBlock: /\$\$([\s\S]*?)\$\$\n?/g,
    latexBlock2: /\\\[\n([\s\S]*?)\n\s*\\]\n/g,
    // latexBlockOrInline: /\$\$([\s\S]*?)\$\$|\$([^$]*?)\$/g,
  };

  const blocks: Block[] = [];
  let lastIndex = 0;

  while (true) {

    // find the first match (if any) trying all the regexes
    let match: RegExpExecArray | null = null;
    let matchType: keyof typeof regexPatterns | null = null;
    for (const type in regexPatterns) {
      const regex = regexPatterns[type as keyof typeof regexPatterns];
      regex.lastIndex = lastIndex;
      const currentMatch = regex.exec(text);
      if (currentMatch && (match === null || currentMatch.index < match.index)) {
        match = currentMatch;
        matchType = type as keyof typeof regexPatterns;
      }
    }
    if (match === null)
      break;

    // anything leftover before the match is text
    if (match.index > lastIndex)
      blocks.push({ type: 'text', content: text.slice(lastIndex, match.index) });

    // add the block
    switch (matchType) {
      case 'codeBlock':
        const blockTitle: string = (match[1] || '').trim();
        const blockCode: string = match[2].trim();
        const blockEnd: string = match[3];
        blocks.push({ type: 'code', blockTitle, blockCode, complete: blockEnd.startsWith('```') });
        break;

      case 'latexBlock':
      case 'latexBlock2':
        const latex: string = match[1];
        blocks.push({ type: 'latex', latex });
        break;
    }

    // advance the pointer
    lastIndex = match.index + match[0].length;
  }

  // remainder is text
  if (lastIndex < text.length)
    blocks.push({ type: 'text', content: text.slice(lastIndex) });

  return blocks;
}